package us.luosl.hadoop.mr.join;

import org.apache.commons.io.FileUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.*;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * reduce join
 * 在map阶段, 把关键字作为key输出，并在value中标记出数据是来自data1还是data2。
 * 因为在shuffle阶段已经自然按key分组，
 * reduce阶段，判断每一个value是来自data1还是data2,在内部分成2组，做集合的乘积。
 * 这种方法有2个问题：
 *1, map阶段没有对数据瘦身，shuffle的网络传输和排序性能很低。
 *2, reduce端对2个集合做乘积计算，很耗内存，容易导致OOM。
 * Created by luosl on 2018/8/31.
 */
public class ReduceSideJoin {


    public static class ReduceSideJoinMapper extends Mapper<Object, Text, IntWritable, JoinWritable>{
        @Override
        protected void map(Object key, Text value, Context context) throws IOException, InterruptedException {
            // 根据文件名称来区分 表名
            FileSplit inputSplit = (FileSplit) context.getInputSplit();
            String fileName = inputSplit.getPath().getName();
            String[] arr = value.toString().split(",");
            if (fileName.startsWith("account")){
                int accountId = Integer.parseInt(arr[0]);
                int userId = Integer.parseInt(arr[1]);
                double balance = Double.parseDouble(arr[2]);
                JoinWritable jw = new JoinWritable();
                jw.setTableName("account")
                        .addData(new Text("accountId"), new IntWritable(accountId))
                        .addData(new Text("balance"), new DoubleWritable(balance))
                        .addData(new Text("userId"), new IntWritable(userId));
                context.write(new IntWritable(userId), jw);
            }else if (fileName.startsWith("user")){
                int userId = Integer.parseInt(arr[0]);
                String nickName = "null";
                if(arr.length > 1){
                    nickName = arr[1];
                }
                JoinWritable jw = new JoinWritable();
                jw.setTableName("user")
                        .addData(new Text("userId"), new IntWritable(userId))
                        .addData(new Text("nickName"), new Text(nickName));
                context.write(new IntWritable(userId), jw);
            }
        }
    }

    public static class ReduceSideJoinReducer extends Reducer<IntWritable, JoinWritable, IntWritable, JoinWritable>{

        @Override
        protected void reduce(IntWritable key, Iterable<JoinWritable> values, Context context)
                throws IOException, InterruptedException {
            List<JoinWritable> uws = new ArrayList<JoinWritable>();
            List<JoinWritable> aws = new ArrayList<JoinWritable>();

            for(JoinWritable jw: values){
                JoinWritable copyJw = new JoinWritable()
                        .setTableName(jw.getTableName())
                        .setData(jw.getData());
                if("user".equals(copyJw.getTableName())){
                    uws.add(copyJw);
                }else if("account".equals(copyJw.getTableName())){
                    aws.add(copyJw);
                }
            }

            for(JoinWritable uw: uws){
                IntWritable keys = new IntWritable(key.get());
                for(JoinWritable aw: aws){
                    context.write(keys, uw.setData(aw.getData()));
                }
            }

        }
    }

    public static void main(String[] args) throws Exception {
        // hadoop jar /mr-1.0-SNAPSHOT.jar us.luosl.mr.join.ReduceSideJoin /luosl/mr/join/account.csv /luosl/mr/join/user.csv  /luosl/mr/join/out
        File out = new File("out");
        if(out.exists()){
            FileUtils.forceDelete(out);
        }

        Configuration conf = new Configuration();
        // 关闭输出压缩
        conf.set("mapreduce.output.fileoutputformat.compress", "false");

        Job job = Job.getInstance(conf, "reduce side join");
        job.setJarByClass(ReduceSideJoin.class);
        job.setMapperClass(ReduceSideJoin.ReduceSideJoinMapper.class);
        job.setReducerClass(ReduceSideJoin.ReduceSideJoinReducer.class);
        job.setOutputKeyClass(IntWritable.class);
        job.setOutputValueClass(JoinWritable.class);

        FileInputFormat.addInputPath(job, new Path(args[0]));
        FileInputFormat.addInputPath(job, new Path(args[1]));
        FileOutputFormat.setOutputPath(job, new Path(args[2]));

        System.exit(job.waitForCompletion(true) ? 0 : 1);
    }
}
